using System;
using System.Collections.Generic;
using System.Text;
using System.Security.Cryptography;
using System.Web;
using System.Data.SqlTypes;

namespace ECommerce.Library
{
    public class Security
    {
        // Check list for security filter level one and two
        private static char[] levelOneDisallowed = { '<', '>' };
        private static char[] levelTwoDisallowed = { '<', '>', '\'', '\"', '\\', '/', '#', '!', '$' };
        private static string[] levelThreeDisallowed = { "update", "delete", "drop", "alter", "insert", "truncate", "execute", "exec","and","or" }; 

        public Security() { }

        public static string Encrypt(string toEncrypt, string key, bool useHashing)
        {
            byte[] keyArray;
            byte[] toEncryptArray = UTF8Encoding.UTF8.GetBytes(toEncrypt);

            //If hashing use get hashcode regards to your key

            if (useHashing)
            {
                MD5CryptoServiceProvider hashmd5 = new MD5CryptoServiceProvider();
                keyArray = hashmd5.ComputeHash(UTF8Encoding.UTF8.GetBytes(key));
                //Always release the resources and flush data of the Cryptographic service provide. Best Practice

                hashmd5.Clear();
            }
            else
                keyArray = UTF8Encoding.UTF8.GetBytes(key);

            TripleDESCryptoServiceProvider tdes = new TripleDESCryptoServiceProvider();
            //set the secret key for the tripleDES algorithm

            tdes.Key = keyArray;
            //mode of operation. there are other 4 modes. We choose ECB(Electronic code Book)

            tdes.Mode = CipherMode.ECB;
            //padding mode(if any extra byte added)


            tdes.Padding = PaddingMode.PKCS7;

            ICryptoTransform cTransform = tdes.CreateEncryptor();
            //transform the specified region of bytes array to resultArray

            byte[] resultArray = cTransform.TransformFinalBlock(toEncryptArray, 0, toEncryptArray.Length);
            //Release resources held by TripleDes Encryptor

            tdes.Clear();
            //Return the encrypted data into unreadable string format

            return Convert.ToBase64String(resultArray, 0, resultArray.Length);
        }

        public static string Decrypt(string cipherString, string key, bool useHashing)
        {
            byte[] keyArray;
            //get the byte code of the string

            byte[] toEncryptArray = Convert.FromBase64String(cipherString);

            if (useHashing)
            {
                //if hashing was used get the hash code with regards to your key

                MD5CryptoServiceProvider hashmd5 = new MD5CryptoServiceProvider();
                keyArray = hashmd5.ComputeHash(UTF8Encoding.UTF8.GetBytes(key));
                //release any resource held by the MD5CryptoServiceProvider

                hashmd5.Clear();
            }
            else
            {
                //if hashing was not implemented get the byte code of the key

                keyArray = UTF8Encoding.UTF8.GetBytes(key);
            }

            TripleDESCryptoServiceProvider tdes = new TripleDESCryptoServiceProvider();
            //set the secret key for the tripleDES algorithm

            tdes.Key = keyArray;
            //mode of operation. there are other 4 modes. We choose ECB(Electronic code Book)


            tdes.Mode = CipherMode.ECB;
            //padding mode(if any extra byte added)

            tdes.Padding = PaddingMode.PKCS7;

            ICryptoTransform cTransform = tdes.CreateDecryptor();
            byte[] resultArray = cTransform.TransformFinalBlock(toEncryptArray, 0, toEncryptArray.Length);
            //Release resources held by TripleDes Encryptor                

            tdes.Clear();
            //return the Clear decrypted TEXT

            return UTF8Encoding.UTF8.GetString(resultArray);
        }

        /// <summary>
        /// User inputs are filtered according to security demand to eliminate security risk
        /// of harmful input. This method is used internally by bind methods
        /// </summary>
        /// <param name="inputText">The original inpute text</param>
        /// <param name="level">Level of security check</param>
        /// <returns>Filtered input text</returns>
        private static string FilterInputText(string inputText, SecurityLevel level)
        {
            // First Html encode the input text
            StringBuilder encoded = new StringBuilder(HttpContext.Current.Server.HtmlEncode(inputText));
          
            // If Level is one or two, the listed characters in the corresponding 
            // security checklist if found are replaced with white space and if
            // level is three only letter, digit, white space and punctuations are allowed
            if (level == SecurityLevel.Zero)
            {
                // no need to filter anything
                //return inputText;
            }
            else if (level == SecurityLevel.One)
            {
                //for (int i = 0; i < levelOneDisallowed.Length; i++)
                //{
                //    encoded = encoded.Replace(levelOneDisallowed[i], ' ');
                //}
            }
            else if (level == SecurityLevel.Two)
            {
                //for (int i = 0; i < levelTwoDisallowed.Length; i++)
                //{
                //    encoded = encoded.Replace(levelTwoDisallowed[i], ' ');
                //}
            }
            else if (level == SecurityLevel.Three)
            {              
                //for (int i = 0; i < levelThreeDisallowed.Length; i++)
                //{
                //    encoded = encoded.Replace(levelThreeDisallowed[i], "["+levelThreeDisallowed[i]+"]");
                //}

            }
            else
            {
                for (int i = 0; i < encoded.Length; i++)
                {
                    if (!char.IsLetterOrDigit(encoded[i]) || !char.IsPunctuation(encoded[i]) || 
                        !char.IsWhiteSpace(encoded[i]) )
                    {
                        encoded[i] = ' ';
                    }
                }
            }
            return encoded.ToString();
        }


        private static string FilterOutputText(string inputText, SecurityLevel level)
        {
           //if (level == SecurityLevel.Three)
           // {             
           //     for (int i = 0; i < levelThreeDisallowed.Length; i++)
           //     {
           //         inputText = inputText.Replace("[" + levelThreeDisallowed[i] + "]", levelThreeDisallowed[i]);
           //     }
           // }
            return inputText;
        }

        /// <summary>
        /// Filters string field value according to security level
        /// </summary>
        /// <param name="input">Input data text.</param>
        /// <param name="securityLevel">Level of security check</param>
        /// <returns>Filtered string</returns>
        public static string BindStringInput(string input, SecurityLevel securityLevel)
        {
            if (!string.IsNullOrEmpty(input))
                return FilterInputText(input, securityLevel);
            else
                return string.Empty;
        }

        public static string BindStringOutput(string input, SecurityLevel securityLevel)
        {
            if (!string.IsNullOrEmpty(input))
                return FilterOutputText(input, securityLevel);
            else
                return string.Empty;
        }
        /// <summary>
        /// Parses DateTime field value from it's string representation
        /// according to security level
        /// For example, if value is "11/12/2008", DateTime object with this value is retured.
        /// But before that it filters out the input data according to 
        /// security level to ensure security.
        /// </summary>
        /// <param name="input">Input data text. Expects date time value string</param>
        /// <param name="securityLevel">Level of security check</param>
        /// <returns>Returns DateTime data</returns>
        public static DateTime BindDateTimeInput(string input, SecurityLevel securityLevel)
        {
            DateTime value = (DateTime)SqlDateTime.MinValue;
            if (!string.IsNullOrEmpty(input))
                return (DateTime.TryParse(FilterInputText(input, securityLevel), out value) ? value : (DateTime)SqlDateTime.MinValue);
            else
                return value;
        }

        /// <summary>
        /// Parses Integer field value from it's string representation
        /// according to security level
        /// For example, if value is "12", 12 is retured.
        /// But before that it filters out the input data according to 
        /// security level to ensure security.
        /// </summary>
        /// <param name="input">Input data text. Expects integer value string</param>
        /// <param name="securityLevel">Level of security check</param>
        /// <returns>Returns integer data</returns>
        public static int BindIntegerInput(string input, SecurityLevel securityLevel)
        {
            int value = 0;
            if (!string.IsNullOrEmpty(input))
                return (int.TryParse(FilterInputText(input, securityLevel), out value) ? value : 0);
            else
                return value;
        }

        /// <summary>
        /// Parses Double field value from it's string representation
        /// according to security level
        /// For example, if value is "10.53", 10.53d is retured.
        /// But before that it filters out the input data according to 
        /// security level to ensure security.
        /// </summary>
        /// <param name="input">Input data text. Expects double value string</param>
        /// <param name="securityLevel">Level of security check</param>
        /// <returns>Returns double data</returns>
        public static double BindDoubleInput(string input, SecurityLevel securityLevel)
        {
            double value = 0.0;
            if (!string.IsNullOrEmpty(input))
                return (double.TryParse(FilterInputText(input, securityLevel), out value) ? value : 0.0);
            else
                return value;
        }

        /// <summary>
        /// Parses boolean field value from it's string representation
        /// according to security level
        /// For example, if value is "True", boolean true is retured.
        /// But before that it filters out the input data according to 
        /// security level to ensure security.
        /// </summary>
        /// <param name="input">Input data text. Expects either True or False.</param>
        /// <param name="securityLevel">Level of security check</param>
        /// <returns>Returns boolean data</returns>
        public static bool BindBooleanInput(string input, SecurityLevel securityLevel)
        {
            bool value = false;
            if (!string.IsNullOrEmpty(input))
                return (bool.TryParse(FilterInputText(input, securityLevel), out value) ? value : false);
            else
                return value;
        }
    }

    /// <summary>
    /// Enumaration to indicate security level
    /// </summary>
    public enum SecurityLevel
    {
        Zero = 0,
        One = 1,
        Two = 2,
        Three = 3
    }
}
